﻿using System;
using System.Collections.Generic;
using Test30.Core;

namespace Test30.Engine
{
    public static class Rng
    {
        public static float Float(float max)
        {
            if (max < 0.0f) throw new ArgumentOutOfRangeException($"The max must be zero or greater.");

            return (float)_rngRandom.NextDouble() * max;
        }

        public static float Float(float min, float max)
        {
            if (max < min) throw new ArgumentOutOfRangeException($"The max must be min or greater.");

            return Float(max - min) + min;
        }
        public static T Item<T>(IList<T> items)
        {
            return items[Int(items.Count)];
        }

        public static int Int(int max)
        {
            return _rngRandom.Next(max);
        }

        public static bool OneIn(int chance)
        {
            if (chance <= 0) throw new ArgumentOutOfRangeException($"The chance must be one or greater.");

            return Int(chance) == 0;
        }

        public static int Roll(int dice, int sides)
        {
            if (dice <= 0) throw new ArgumentOutOfRangeException(nameof(dice), "The argument \"dice\" must be greater than zero.");
            if (sides <= 0) throw new ArgumentOutOfRangeException(nameof(sides), "The argument \"sides\" must be greater than zero.");

            int total = 0;

            for (int i = 0; i < dice; i++)
            {
                total += Int(1, sides + 1);
            }

            return total;
        }

        public static void Seed(int seed)
        {
            _rngRandom = new Random(seed);
        }

        public static int Taper(int start, int increment, int chance, int outOf)
        {
            if (increment == 0) throw new ArgumentOutOfRangeException(nameof(increment), "The increment cannot be zero.");
            if (chance <= 0) throw new ArgumentOutOfRangeException(nameof(chance), "The chance must be greater than zero.");
            if (chance >= outOf) throw new ArgumentOutOfRangeException(nameof(chance), "The chance must be less than the range.");
            if (outOf <= 0) throw new ArgumentOutOfRangeException(nameof(outOf), "The range must be positive.");

            int value = start;

            while (Int(outOf) < chance)
            {
                value += increment;
            }

            return value;
        }

        public static int TriangleInt(int center, int range)
        {
            if (range < 0) throw new ArgumentOutOfRangeException(nameof(range), "The argument \"range\" must be zero or greater.");

            // pick a point in the square
            int x = IntInclusive(range);
            int y = IntInclusive(range);

            // figure out which triangle we are in
            if (x <= y)
            {
                // larger triangle
                return center + x;
            }
            else
            {
                // smaller triangle
                return center - range - 1 + x;
            }
        }

        public static Vec Vec(Vec size)
        {
            return new Vec(Int(size.X), Int(size.Y));
        }

        public static Vec Vec(Rect rect)
        {
            return new Vec(Int(rect.Left, rect.Right), Int(rect.Top, rect.Bottom));
        }

        public static int Int(int min, int max)
        {
            return Int(max - min) + min;
        }

        public static int IntInclusive(int max)
        {
            return _rngRandom.Next(max + 1);
        }

        public static int IntInclusive(int min, int max)
        {
            return IntInclusive(max - min) + min;
        }
        public static int WalkLevel(int level)
        {
            int result = level;

            // stack a few triangles to approximate a bell
            for (int i = 0; i < Math.Min(5, level); i++)
            {
                // the width of the triangle is based on the level
                result += TriangleInt(0, 1 + (level / 20));
            }

            // also have an exponentially descreasing change of going out of depth
            while (OneIn(10))
            {
                result += 1 + Int(2 + (level / 5));
            }

            return result;
        }

        private static Random _rngRandom = new Random();
    }
}
